using System;
using System.Linq;
using System.Reflection;
using EPiServer.DataAbstraction;
using EPiSugar.Configuration;
using EPiSugar.EntityAdapter.PropertyBinders;
using EPiSugar.EntityAdapter.PropertyBinders;

namespace EPiSugar.EntityAdapter
{
    public class ModelBuilder
    {
        private readonly TypeMappingCollection typeMappings;
        private readonly PageTypeCollection pageTypes;

        public ModelBuilder(TypeMappingCollection typeMappings, PageTypeCollection pageTypes)
        {
            this.typeMappings = typeMappings;
            this.pageTypes = pageTypes;
        }

        public EntityModel Build()
        {
            EntityModel model = new EntityModel();

            AddConfiguredMappings(model);
            AddFallbackMappings(model);

            return model;
        }

        private void AddConfiguredMappings(EntityModel model)
        {
            foreach(TypeMappingElement element in typeMappings)
            {
                PageType pageType = (from t in pageTypes where t.ID == element.PageTypeID select t).First();
                Type entityType = Type.GetType(element.EntityType);
                var mapping = CreateMapping(pageType, entityType, element.AutoBind);
                model.AddMapping(mapping);
            }
        }

        private void AddFallbackMappings(EntityModel model)
        {
            Type fallbackType = Type.GetType(typeMappings.FallbackType);
            var unmappedTypes = (from t in pageTypes where !(from m in model.Mappings.Values select m.PageTypeID).Contains(t.ID) select t);
            foreach (PageType pageType in unmappedTypes)
            {
                var mapping = CreateMapping(pageType, fallbackType, true);
                model.AddMapping(mapping);
            }
        }

        public PageMapping CreateMapping(PageType pageType, Type entityType, bool autoBind)
        {
            if (entityType == null) throw new ArgumentNullException("The entity type defined in the configuration was not found.", "entityType");

            var mapping = new PageMapping(pageType.ID, pageType.Name, entityType);
            var properties = mapping.EntityType.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                IPropertyBinder propertyMapping = GetAttributeBinder(property, pageType.Definitions);
                if (propertyMapping == null && autoBind)
                    propertyMapping = GetDefaultBinder(property, pageType.Definitions);

                mapping.AddBinder(propertyMapping);
            }

            return mapping;
        }

        private IPropertyBinder GetAttributeBinder(PropertyInfo property, PageDefinitionCollection propertyDefinitions)
        {
            foreach (IBinderFactory attribute in property.GetCustomAttributes(typeof(IBinderFactory), false))
                return attribute.CreateBinder(propertyDefinitions, property);
            return null;
        }

        private IPropertyBinder GetDefaultBinder(PropertyInfo property, PageDefinitionCollection propertyDefinitions)
        {
            foreach (var pd in propertyDefinitions)
                if (pd.Name == property.Name)
                    return new PropertyBinder(pd.Name, property);
            return null;
        }
    }
}